﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ExtremeSwank.OpenId;
using Db4objects.Db4o;
using Db4objects.Db4o.Linq;
using Db4objects.Db4o.NativeQueries;

namespace ExtremeSwank.OpenId.Persistence
{
    /// <summary>
    /// Persists OpenID Stateful session data using a db4o database.
    /// </summary>
    /// <remarks>
    /// Implemented using Linq queries, so requires db4o 7.2, and .NET 3.5 or higher.
    /// </remarks>
    public class Db4oSessionManager : ISessionPersistence, IDisposable
    {
        IObjectContainer db;
        bool directFile;
        string SessionID;

        /// <summary>
        /// Creates a new instance of Db4oSessionManager using an already-open IObjectContainer.
        /// </summary>
        /// <param name="container">IObjectContainer to use.</param>
        /// <param name="sessionid">The ID of the session.</param>
        public Db4oSessionManager(IObjectContainer container, string sessionid)
        {
            db = container;
            SessionID = sessionid;
        }

        /// <summary>
        /// Create a new instance of Db4oSessionManager.
        /// </summary>
        /// <param name="databasefile">The database file to directly open.</param>
        /// <param name="sessionid">The ID of the session.</param>
        public Db4oSessionManager(string databasefile, string sessionid)
        {
            db = Db4oFactory.OpenFile(databasefile);
            directFile = true;
            SessionID = sessionid;
        }

        int GetNonce()
        {
            DateTime now = DateTime.UtcNow;

            int nonce = -1;

            var result = (from s in db.Query<Db4oSessionObject>()
                         where (s.SessionID == SessionID &&
                         s.Expiration > now)
                         select s).ToList();
            if (result.Count > 0)
            {
                nonce = result[0].Nonce;
            }

            return nonce;
        }

        void SetNonce(int nonce)
        {
            Db4oSessionObject so = new Db4oSessionObject();

            var result = (from s in db.Query<Db4oSessionObject>()
                          where (s.SessionID == SessionID)
                          select s).ToList();

            if (result.Count > 0)
            {
                so = result[0];
            }

            if (nonce == -1)
            {
                if (so != null) { db.Delete(so); }
            }
            else
            {
                so.SessionID = SessionID;
                so.Nonce = nonce;
                so.Expiration = DateTime.UtcNow.AddMinutes(5);
                db.Store(so);
            }

            db.Commit();
        }

        #region ISessionPersistence Members

        /// <summary>
        /// Gets or sets the nonce value recorded in the database.
        /// </summary>
        public int Nonce
        {
            get
            {
                return GetNonce();
            }
            set
            {
                SetNonce(value);
            }
        }

        #endregion
    
        #region IDisposable Members

        /// <summary>
        /// If the database file was opened directly, clean up and close the file.
        /// </summary>
        public void Dispose()
        {
            if (directFile)
            {
                if (db != null)
                {
                    db.Close();
                    db.Dispose();
                    db = null;
                }
            }
        }

        #endregion
    }

    internal class Db4oSessionObject
    {
        public int Nonce { get; set; }
        public DateTime Expiration { get; set; }
        public string SessionID { get; set; }
    }
}
